前文介绍了 source map 在开发调试中的作用,以及不同的 source map 策略对构建时间和调试效果的影响。可以观察项目里在开发和生产环境下使用的是哪一种 source map 类型,因为很多时候使用预设好的脚手架工具后,这些细节可能不太被关注到。
Mock 在程序设计中是指使用模拟(Mock)的对象来替代真实对象,以测试其他对象的行为。而在前端开发流程中,Mock 通常是指模拟数据(俗称假数据)以及生成和使用模拟数据的工具与流程。那么为什么要使用 Mock 数据?是因为在实际中,经常遇到以下令人困扰的问题。
在一个前后端分离的项目开发流程中,项目的开发时间通常分为三块:前端开发时间 t1,后端开发时间 t2,前后端联调时间 t3。理想情况下,整体的项目开发时间是 ≤ max(t1, t2) + t3,即前后端同时开发,两端都开发完成后进入联调。甚至再进一步,为了提高效率,也可以将整个开发流程按功能点进行更细粒度地拆分,即在开发时间内,也可以在部分功能开发完成后立即进行这一部分的联调,以期望利用碎片化的时间来减少后期完整联调的时间。
但现实中,随着项目前端交互流程的日益复杂化,在开发流程中,前端往往需要依赖一定的数据模型来组织页面与组件中的交互流程,而数据模型又依赖着后端提供的 API 接口。也就是说,在新项目新功能的开发流程中,前端的开发时间不只取决于自身开发部分的耗时,还依赖于后端开发完成的时间。那么如何实现前端的无依赖独立开发以提升效率?
假设在后端实际 API 功能完成之前,能获得对应的模拟数据作为接口的返回值来处理前端交互中的数据模型,待开发完成进入联调后再将假数据的部分切换到真实的后端服务接口数据,这样开发阶段的阻碍问题就解决了。事实上,使用 Mock 数据已成为前端开发流程中必不可少的一环。
对于在前端开发中使用 Mock 数据的需求,实现路径有很多,例如:
其中,第一种书写静态返回数据的方式和第二种开发服务端返回假数据的方式可能是前端从直觉上最容易理解和实践的。但对于第一种方案而言,代码的维护成本、复杂接口的数据实现和处理以及特殊字段的额外处理等因素,都导致它在实际开发过程中的使用场景非常局限;而第二种方案仍然依赖后端提供相应的服务,在独立性、稳定性与灵活性方面也难以达到 Mock 方案达成前端独立开发的要求。
剩下的两种实现方式则各有其适用场景和局限性:在后端已提供接口文档,而团队未使用接口管理工具的情况下,第三种本地化的 Mock 工具使用成本更低;而反之,第四种则有一定的前期搭建和维护成本,但在前后端达成一致使用接口管理工具的情况下,整体效率更高。
除了考虑不同实现路径外,对于相同的实现方式,可选择的工具也各有不同。在讨论具体的 Mock 方案之前,先来看选择的参考依据:
Mock.js 是前端领域流行的 Mock 数据生成工具之一,后续许多功能更丰富的工具和系统在各自的 Mock 功能部分都将它作为基础设施。
Mock.js 的核心能力是定义了两类生成模拟数据的规范:数据模板定义规范(Data Template Definition, DTD)与数据占位符定义规范(Data Placeholder Definition, DPD),以及实现了应用相应规范生成模拟数据的方法。
数据模板定义规范(DTD)
数据模板定义规范约定了可以通过“属性名|生成规则:属性值”这样的格式来生成模拟数据,例如(完整示例代码参见 04_mock):
`Mock.mock({
"number|1-100": 1
})
//Result: number为1-100内随机数,例如{number: 73}
Mock.mock({
"boo|1-100": true
})
//Result: boo为true或false,其中true的概率为1%,例如{boo: false}
Mock.mock({
"str|1-100": '1'
})
//Result: str为1-100个随机长度的字符串'1'。例如{str: '11111'}
`从上面的例子可以看到,属性名只是作为生成数据的固定名称,而同样的生成规则下,随着属性值的不同,生成规则对应的内部处理逻辑也不同。在 Mock.js 中,共定义了 7 种生成规则:min-max、min-max.dmin-dmax、min-max.dcount、count、count.dmin-dmax、count.dcount、+step。根据这 7 种规则,再结合不同数据类型的属性值,就可以定义出任意需要的随机数据生成逻辑。
数据占位符定义规范 (DPD)
数据占位符定义规范则是对于随机数据的一系列常用类型预设,书写格式是'@占位符(参数 [, 参数] )'。如以下例子:
`Mock.mock('@email')
//Result: 随机单词连接成的email数据,例如:"n.clark@miller.io"
Mock.mock('@city(true)')
//Result: 随机中国省份+省内城市数据,例如:"吉林省 辽源市"
Mock.mock({'aa|1-3':['@cname()']})
//Result: aa值为随机3个中文姓名的数组,例如{aa: ['张三','李四','王五']}
Random.image('200x100', '#894FC4', '#FFF', 'png', '!')
//Result: 利用dummyimage库生成的图片url, "http://dummyimage.com/200x100/894FC4/FFF.png"
`从这些例子中可以看到,占位符既可以用于单独返回指定类型的随机数据,又能结合数据模板,作为模板中属性值的部分来生成更复杂的数据类型。Mock.js 中定义了 9 大类共 42 种占位符,更多占位符的说明和示例可以从官网中查找和使用。
其他功能
除了提供生成模拟数据的规范和方法外,Mock.js 还提供了一些辅助功能,包括:
Faker.js 是另一个较热门的模拟数据生成工具。与 Mock.js 相比,Faker.js 主要提供的是指定类型的随机数据,对应 Mock.js 中的占位符类型数据。在 API 的使用方面较直观,使用示例如下:
`//单独使用api方法
var randomName = faker.name.findName(); // Rowan Nikolaus
var randomEmail = faker.internet.email(); // Kassandra.Haley@erich.biz
var randomCard = faker.helpers.createCard(); // random contact card containing many properties
//使用fake来组合api
faker.fake("{{name.lastName}}, {{name.firstName}} {{name.suffix}}")
// outputs: "Marks, Dean Sr."
`注:示例中的 API 为旧版 faker.js 的用法。原仓库已于 2022 年被原作者删除并破坏,现由社区以 @faker-js/faker 延续维护,新项目建议直接使用该包(API 有所调整,如 faker.name.findName() 已更名为 faker.person.fullName())。
除了在数据生成的规则上没有 Mock.js 的数据模板规则那样灵活以外,对于一般的数据模拟需求,Faker.js 已能很好地满足。此外,它还支持多种语言的本地化包,满足国际化站点开发的需求。
以上两种工具在实际项目使用中,都需要在项目本地编写数据生成模板或方法,而后根据一定的方式拦截 API 请求并指向本地生成的 Mock 数据。拦截的方法可以类似 Mock.js 的覆盖 API 调用对象,也可以是通过网络代理将后端域名指向本地目录。
这种本地植入模拟数据生成器的方式可以在一定程度上提升前端独立开发调试的效率,但从整体前后端工作的效率上来看,并非最佳选择:
对于第一点,有一种解决思路是基于 TypeScript 接口类型描述对象来自动生成模拟数据。而对于后面两点,解决方案是将接口文档和 Mock 数据服务,以及接口测试工具结合在一起,合并成相关功能链路集成的平台和工具,例如下面介绍的两个:
YApi 定义是开发、产品、测试人员共同使用的接口管理服务,其功能特点主要包括:
注:YApi 开源版本近年来已基本停止维护,且存在已知安全问题,选型时建议评估 Apifox 等替代方案。

Apifox 是一个桌面应用类的接口管理工具。与 YApi 相比,除了使用方式不同外,其主要特点还包括:

以上两种接口管理工具都包含了提供对应接口的 Mock 服务的能力。相比于单独提供生成 Mock 数据能力的 Mock.js 和 Faker.js,这类工具解决了接口定义与 Mock 数据脱离的问题:
通过这样的流程串联,解决前后端开发过程中的接口联调效率问题。
本文讨论了 Mock 工具在前后端分离开发流程中起到的作用,以及选择 Mock 方案的一般考量标准,并重点介绍了几种 Mock 工具:有专注于提供生成模拟数据这一核心能力的 Mock.js 和 Faker.js,也有更平台化的内置 Mock 功能的 YApi 和 Apifox。在项目开发过程中,可以根据自身项目的情况来选择使用。
作为延伸思考:项目开发中是否用到了本地或服务化的 Mock 工具?如果用到了,可以梳理其使用感受与利弊。